<?php
include_once '../utils/WCDBLayer.php';
include_once '../exceptions/WCException.php';
include_once '../conf/Conf.php';

/**
 * Base class for all database drive objects to extend.  Provides basic functionality of all objects based on database tables.
 * 
 * @author daniel.klco
 * @version 20090226
 */
abstract class WCPersistantObject{
	private $attributes = Array();
	private $id;
	protected $table;
	protected $isSaved = false;
	protected $isCurrent = false;
	protected $db;
	
	/**
	 * Returns true if the object can be saved to the database.
	 * 
	 * @param true if the object is new eg. going to be inserted, false if it is going to be updated
	 * @return true if the object can be saved, false otherwise
	 */
	protected abstract function canSave($new);

	/**
	 * Returns all of the accepted attributes for the object.  These attributes will be saved to the database
	 * when the object is saved.  The key is the name of the attribute the value is whether or not it requires
	 * quotes.
	 * 
	 * @return An array of the attributes for the object.
	 */
	protected abstract function objectAttributes();
	
	/**
	 * Returns all of the required attributes for the object.  If these attributes are not set, the object
	 * will throw an exception while attempting to save.
	 * 
	 * @return An array of the required attributes for the object.
	 */
	protected abstract function requiredAttributes();
	
	/**
	 * Creates the table in the for this object.  Passing in the delete flag as true will cause the function to
	 * delete the table if it already exists.  If this flag is false or omitted the function will throw an error if the 
	 * table already exists.
	 * 
	 * @param $delete if true the table will be deleted if it already exists
	 * @return none
	 */
	protected function createTable($delete=false){
		if(!$this->db instanceof WCDBLayer){
			throw new WCException(WCException::$CLASS_ERROR,"Invalid class passed in, should be WCDBLayer class or descendant.");
		}
		if($this->db->tableExists($this->table)){
			if(!$delete){
				throw new WCException(WCException::$DB_QUERY_ERROR,"Table already exists.");
			}else{
				$this->db->executeQuery('DROP TABLE '.$this->table);
			}
		}
	}
	
	/**
	 * Constructor - sets the table name and the database connection for the object.  If an id is passed in, the constructor will load data 
	 * from the database.
	 * 
	 * @return none
	 */
	protected function __construct($table,$db,$id){
		$this->table = $table;
		$this->db = $db;
		if($id != NULL){
			$this->id = $id;
			$this->load();
		}
	}
	
	/**
	 * Sets the id of the object to null and the attributes to an empty array.  Used to clear an object.
	 * 
	 * @return none
	 */
	protected function unload(){
		$this->id = NULL;
		$this->attributes = Array();
	}

	/**
	 * Returns true if the specified attribute is set, does not check if the attribute has a non-empty value.
	 * 
	 * @param $key
	 * @return Returns true if the specified attribute is set.
	 */
	public function isAttrSet($key){
		return isset($this->attributes[$key]);
	}
	
	public function createRelationship($otherObj,$type){
		$rel = new WCRelation($this->db);
		$rel->setAttribute('relation_type',$type);
		$rel->setAttribute('parent_id',$this->getId());
		//TODO: I am thinking this might always be WCPersistantObject...
		$rel->setAttribute('parent_type',get_class($this));
		$rel->setAttribute('child_id',$otherObj->getId());
		$rel->setAttribute('child_type',get_class($otherObj));
		$rel->save();
	}
	
	/**
	 * Deletes the object data from the database and unloads the object attributes.
	 * 
	 * @param $db
	 * @return none
	 */
	public function delete(){
		if(!$this->db instanceof WCDBLayer){
			throw new WCException(WCException::$CLASS_ERROR,"Invalid class passed in, should be WCDBLayer class or descendant.");
		}
		$this->db->executeQuery('DELETE FROM '.$this->table.' WHERE id=\''.$this->id.'\'');
		$this->unload();
	}
	
	public function deleteRelationship($otherObj,$type){
		$rel = WCRelation::retrieve($this->id,get_class($this),$otherObj->getId(),get_class($otherObj),$type);
		
		if($rel != false){
			$rel->delete();
		}
	}
	
	public function dump(){
		echo 'Object ID: '.$this->id.'<br/>';
		foreach($this->attributes as $key=>$value){
			echo $key.': '.$value.'<br/>';
		}
	}
	
	/**
	 * Returns the value from the attribute HashMap matching the key.
	 * 
	 * @param $key
	 * @return 
	 */
	public function getAttribute($key){
		return $this->attributes[$key];
	}

	/**
	 * Returns the id of the object.
	 * 
	 * @return id of the object
	 */
	public function getId(){
		return $this->id;
	}
	
	/**
	 * Returns a list of all of the related objects and the respective object types.  
	 * 
	 * @param $relation_type
	 * @return array of related object ids and object types
	 */
	public function getRelated($relation_type=NULL){
		$related = array();
		
		if($relation_type == NULL){
			$query = 'SELECT child_id, child_type FROM wc_relation WHERE parent_id='.$this->getId();
		}else{
			$query = 'SELECT child_id, child_type FROM wc_relation WHERE parent_id='.$this->getId()." AND relation_type='$relation_type'"; 
		}
		
		$result1 = $super->db->executeQuery($query);
		$num = mysql_num_rows($result);
		
		for($i=0;$i<$num;$i++){
			$related[mysql_result($result,$i,'child_id')] = mysql_result($result,$i,'child_type');
			
		}
		
		if($relation_type == NULL){
			$query = 'SELECT parent_id, parent_type FROM wc_relation WHERE child_id='.$this->getId();
		}else{
			$query = 'SELECT parent_id, parent_type FROM wc_relation WHERE child_id='.$this->getId()." AND relation_type='$relation_type'"; 
		}
		
		$result1 = $super->db->executeQuery($query);
		$num = mysql_num_rows($result);
		
		for($i=0;$i<$num;$i++){
			$related[mysql_result($result,$i,'parent_id')] = mysql_result($result,$i,'parent_type');
		}
		return $related;
	}
	
	/**
	 * Returns the value from the attribute HashMap matching the provided key.
	 * 
	 * @param $key
	 * @return returns an array of values for the attribute
	 */
	public function getRepeatingAttribute($key){
		return explode(Conf::$separator,$this->attributes[$key]);
	}
	
	/**
	 * Returns true if the specified attribute is a repeating attribute and false otherwise.
	 * 
	 * @param $key
	 * @return True if the specified attribute is a repeating attribute and false otherwise.
	 */
	public function isRepeatingAttr($key){
		if(strrpos($this->attributes[$key],Conf::$separator) === false){
			return false;
		}
		return true;
	}

	/**
	 * 
	 * @return unknown_type
	 */
	public function load(){
		if(!$this->db instanceof WCDBLayer){
			throw new WCException(WCException::$CLASS_ERROR,"Invalid class passed in, should be WCDBLayer class or descendant.");
		}
		
		$result = $this->db->retrieve($this->table,array_keys($this->objectAttributes()),'id='.$this->id);
		if(mysql_num_rows($result) != 1){
			throw new WCException(WCException::$DB_QUERY_ERROR,'Unable to load object '.$this->id.' invalid number of rows '.mysql_num_rows($result).' retrieved.');
		}
		foreach(mysql_fetch_assoc($result) as $key => $value){
			$this->attributes[$key]=$value;
		}
		$this->isCurrent = true;
		$this->isSaved = true;
	}
	
	/**
	 * @param $db
	 * @return unknown_type
	 */
	public function save(){
		if(!$this->db instanceof WCDBLayer){
			throw new WCException(WCException::$CLASS_ERROR,"Invalid class passed in, should be WCDBLayer class or descendant.");
		}
		
		//check to make sure the required variables are set
		foreach($this->requiredAttributes() as $attribute){
			if(!array_key_exists($attribute,$this->attributes)){
				throw new WCException(WCException::$MISSING_REQUIRED_ERROR,"Required attribute $attribute missing, cannot save.");
			}
		}
		
		if(!$this->isSaved){
			if(!$this->canSave(true)){
				throw new WCException(WCException::$DB_ALREADY_EXISTS,'Cannot save new object, another object already exists.');
			}
			
			//build the query
			$query = 'INSERT INTO '.$this->table.' SET';
			
			$values = array();
			foreach($this->objectAttributes() as $name => $quotes){
				if($quotes){
					$query .= " $name='?',";
				}else{
					$query .= " $name=?,";
				}
				$values[] = $this->attributes[$name];
			}
			$query = substr($query,0,-1);
			
			$this->db->executeQuery($this->db->prepare($query,$values));
			
			//getting the id from the last insert and setting it as the object id
			$this->id = mysql_insert_id($this->db->getConnection());
		}else{
			if(!$this->canSave(false)){
				throw new WCException(WCException::$DB_ALREADY_EXISTS,'Cannot update object, will cause conflicts with pre-existing object.');
			}
			
			//build the query
			$query = 'UPDATE '.$this->table.' SET';
			$values = array();
			foreach($this->objectAttributes() as $name => $quotes){
				if($quotes){
					$query .= " $name='?',";
				}else{
					$query .= " $name=?,";
				}
				$values[] = $this->attributes[$name];
			}
			$query = substr($query,0,-1);
			$query.=' WHERE id='.$this->id;
			$this->db->executeQuery($this->db->prepare($query,$values));
		}
		$this->isCurrent = true;
		$this->isSaved = true;
		return true;
	}

	/**
	 * 
	 * 
	 * @param $key
	 * @param $value
	 * @return none
	 */
	public function setAttribute($key,$value){
		$this->attributes[$key] = $value;
		$this->isCurrent = false;
	}
	
	/**
	 * 
	 * 
	 * @param $key
	 * @param $value
	 * @return none
	 */
	public function setAttributes($array){
		$this->attributes = array_merge($this->attributes,$array);
		$this->isCurrent = false;
	}
	
	protected function setId($id){
		$this->id = $id;
	}
	
	/**
	 * 
	 * 
	 * @param $key
	 * @param $value
	 * @return none
	 */
	public function setRepeatingAttribute($key,$value){
		$this->attributes[$key] = implode(Conf::$separator,$value);
		$this->isCurrent = false;
	}
}
?>